#![allow(dead_code)]

#[macro_use]
#[allow(dead_code)]
extern crate rouille;

#[macro_use]
extern crate lazy_static;

#[macro_use]
extern crate clap;
extern crate regex;
extern crate chrono;
extern crate encoding;
extern crate ssh2;
use chrono::Local;
use log::info;
use std::fs;
use std::io::Write;
use std::io::Read;
use std::process::Command;
mod handler;
use colored::Colorize;
use uuid::Uuid;
// use handler::{form,json};

use handler::backends::db_protocol;
use handler::broadcast::BroadcastBatchStr;
use handler::check::{info_protocol, init_build};
use handler::client::json_to;
use handler::encrypt::{test_enc, Encryptor};
use handler::tasks;

use handler::client::Reply;
use handler::config::home;

use handler::remote::AccountAction;
use handler::remote::AccountManager;
use handler::remote::RemoteAccount;
use handler::routes;
use handler::utils::{self,daemon_start};
use handler::chains;

use std::process::exit;
// const tmp_data:TmpData = TmpData::new();

#[derive(Clap)]
#[clap(
    version = "1.0",
    author = "f0cklunx",
    about = r#"
    -c 
        /i      handle some info
        /db     handle some db
        /task   handle some task 
        /brd    handle some brd
    -e 
        test    show my host db .
        ping    test if ssh protocol can use .
        put     upload files to remote.
        get     downlod file to local from remote.
        stop    stop running exe in remote.
        connect connect remotes.
        update  update config file.
        down    control remote download url.
        check   check runner in server.
        
"#
)]
struct Opts {
    #[clap(short = "s", long = "server", default_value = "http://localhost:54321")]
    server: String,


    #[clap(short = "p", long = "pwd", default_value = "fuck you world!")]
    pwd: String,

    #[clap(short = "b", long = "brd")]
    brd: bool,

    #[clap(short = "O", long = "log_file", default_value = "/tmp/mutask.log")]
    log: String,

    #[clap(short = "L", long = "host", default_value = "[NONE]")]
    host_key: String,

    #[clap(short = "e", long = "cmd")]
    cmd: Vec<String>,

    // #[clap(short = "c", long = "client", multiple = true)]
    client: Vec<String>,

    #[clap(long = "uninstall")]
    uninstall: bool,
    #[clap(long = "clear")]
    clear: bool,
    #[clap(long = "stop")]
    stop:bool,

    #[clap(long = "daemon")]
    daemon: bool,

    #[clap(short = "t", long = "test")]
    test: bool,
}

fn log_inif() {
    let mut clog = colog::builder();
    clog.format(|buf, record| {
        writeln!(
            buf,
            "{} [{}] - {}",
            Local::now().format("%Y-%m-%dT%H:%M:%S"),
            record.level(),
            record.args()
        )
    });
    // clog.filter(None, log::LevelFilter::Warn);
    clog.init();
}

// fn to_static(t:TmpData) -> &'static TmpData {
//     Box::leak(Box::new(t))
// }

fn run_server(server: &str) {
    // log_inif();
    info!("log init [{}]", "ok".green());
    let sysinfo = init_build();
    info!("init build [{}]", "ok".green());
    info!("Now listening on {} : password : xxxx", server.blue());
    let listen_address: String;
    if server.starts_with("http://") {
        listen_address = server.replace("http://", "");
    } else {
        listen_address = server.to_string();
    }
    #[allow(unreachable_code)]
    rouille::start_server(&listen_address, move |request| {
        // let tx_patch = tx_safe.clone();
        // try_safe_use!(tx_safe);

        // let tmp_db_patch = tmp_db_safe.clone();
        router!(request,
            (POST) (/i) => {
                routes::with_parameter(request,&sysinfo, info_protocol)
            },
            (POST) (/db) => {
                routes::with_parameter(request,&sysinfo, db_protocol)
            },
            (POST) (/task) => {
                routes::with_parameter(request,&sysinfo, tasks::task_protocol)
            },
            (GET) (/alive) => {
                rouille::Response::text(format!("hello, wolrd!"))
            },
            (POST) (/chain) => {
                routes::with_parameter(request,&sysinfo, chains::action_protocol)
            },
            (GET) (/panic) => {
                // If the request's URL is `/panic`, we jump here.
                //
                // This block panics. Fortunately rouille will automatically catch the panic and
                // send back a 500 error message to the client. This prevents the server from
                // closing unexpectedly.
                panic!("Oops!")
            },
            (GET) (/{id: u32}) => {
                // If the request's URL is for example `/5`, we jump here.
                //
                // The `router!` macro will attempt to parse the identfier (eg. `5`) as a `u32`. If
                // the parsing fails (for example if the URL is `/hello`), then this block is not
                // called and the `router!` macro continues looking for another block.
                println!("u32 {:?}", id);

                // For the same of the example we return an empty response with a 400 status code.
                rouille::Response::empty_400()
            },



            (GET) (/{id: String}) => {
                // If the request's URL is for example `/foo`, we jump here.
                //
                // This route is similar to the previous one, but this time we have a `String`.
                // Parsing into a `String` never fails.
                println!("String {:?}", id);

                // Builds a `Response` object that contains "hello, " followed with the value
                // of `id`.
                rouille::Response::text(format!("hello, {}", id))
            },

            // The code block is called if none of the other blocks matches the request.
            // We return an empty response with a 404 status code.
            _ => rouille::Response::empty_404()
        )
    });
}

fn to_static(s: String) -> &'static str {
    Box::leak(s.into_boxed_str())
}

fn client_main(opts: &Opts, addr: &str) {
    let cipher = Encryptor::new(&opts.pwd);
    let res: Reply = match opts.client.len() {
        4 => {
            // let d = (1..4).map(|i| i.parse::String());
            let brd_data: String;
            if opts.client[2].starts_with("@") {
                let file = opts.client[2][1..].to_string();
                if let Some(brd_data_opt) = file.batch_from_file() {
                    brd_data = brd_data_opt;
                } else {
                    info!("no such file : {}", &opts.client[2]);
                    exit(1);
                }
            } else {
                brd_data = opts.client[2].to_string();
            }
            json_to(
                &opts.client[1],
                &brd_data,
                &opts.client[3],
                &opts.client[0],
                Some(addr),
                Some(&cipher),
                Some(opts.brd),
            )
        }
        3 => {
            let brd_data: String;
            if opts.client[2].starts_with("@") {
                let file = opts.client[2][1..].to_string();
                if let Some(brd_data_opt) = file.batch_from_file() {
                    brd_data = brd_data_opt;
                } else {
                    info!("no such file : {}", &opts.client[2]);
                    exit(1);
                }
            } else if opts.client[2].contains("@") && opts.client[2].contains(":") {
                // let mut sit = opts.client[2].split("@");
                // let _ = sit.next().unwrap();
                // let file = sit.next().unwrap();
                if let Some(brd_data_opt) = opts.client[2].batch_from_file_task() {
                    brd_data = brd_data_opt;
                } else {
                    info!("no such file : {}", &opts.client[2]);
                    exit(1);
                }
            } else {
                brd_data = opts.client[2].to_string();
            }
            json_to(
                &opts.client[1],
                &brd_data,
                "",
                &opts.client[0],
                Some(addr),
                Some(&cipher),
                Some(opts.brd),
            )
        }
        2 => json_to(
            &opts.client[1],
            "some",
            "some",
            &opts.client[0],
            Some(addr),
            Some(&cipher),
            Some(opts.brd),
        ),
        _ => json_to(
            "asdf",
            "",
            "",
            "/info",
            Some(addr),
            Some(&cipher),
            Some(opts.brd),
        ),
    };

    println!("{}", res);
}

fn main() {
    log_inif();
    let mut opts = Opts::parse();
    if opts.stop{
        
        let p = std::path::Path::new("/tmp/mutask.pid");
        // log::info!("found {}", p.to_str().unwrap().yellow());
        if p.exists(){
            
            if let Ok(mut f) = fs::File::open(p){
                let mut pid = String::new();
                
                if let Ok(_) = f.read_to_string(&mut pid){
                    log::info!("stop pid: {}",pid.green());
                    let res = Command::new("sh")
                        .arg("-c")
                        .arg(format!("ps aux | grep {}| grep -v '(grep|egrep)' ", pid.trim()))
                        .output()
                        .expect("failed to execute process");
                    if res.stdout.len() > 0{
                        log::info!("found {}", p.to_str().unwrap().yellow());
                        let res = Command::new("sh")
                        .arg("-c")
                        .arg(format!("kill -9 {}", pid.trim()))
                        .output()
                        .expect("failed to execute process");
                        log::info!("stop : {}",std::str::from_utf8(&res.stdout).unwrap());
                        
                    }
                    
                }
                

            }else{
                log::info!("notfound {}", p.to_str().unwrap().yellow());
            }
            let _ = fs::remove_file(p);
            
        }else{
            log::info!("notfound {}", p.to_str().unwrap().yellow());
        }
        
        std::process::exit(0);
    }
    if opts.clear{
        utils::clear_uuid_output();
        std::process::exit(0);
    }

    let cipher = Encryptor::new(&opts.pwd);
    if opts.cmd.len() > 0 && opts.host_key != "[NONE]" {
        let home = dirs::home_dir().unwrap();
        let p = home.join(".config").join("seed").join("cache.db");
        if let Some(acc) = AccountManager::new(p.as_ref()) {
            if opts.cmd[0] == "put" && opts.cmd.len() == 3 {
                let dst = opts.cmd[2].clone();
                for f_str in &opts.cmd[1..] {
                    let f_str = f_str.clone();
                    acc.search_host(&opts.host_key).upload(&f_str, &dst);
                }
            } else if opts.cmd[0] == "ping" {
                acc.search_host(&opts.host_key).with(move |r| {
                    log::info!(
                        "{} {}@{}",
                        "online".bold().on_green(),
                        r.user.bright_yellow().on_blue(),
                        r.host.bright_white().on_blue(),
                    );
                });
            } else if opts.cmd[0] == "test" {
                for r in acc.search_host(&opts.host_key) {
                    log::info!(
                        "{}@{}{} | {} ssh port :{}",
                        "[db]".bold().on_green(),
                        r.user.yellow().on_red(),
                        r.host.bright_white().on_blue(),
                        r.pwd.black().on_yellow(),
                        r.port,
                    );
                }
            } else if opts.cmd[0] == "stop" {
                // use std::sync::Arc;
                let rrr = opts.cmd[1].clone();
                let runner: &'static str = to_static(rrr);
                acc.search_host(&opts.host_key).with(move |mut r| {
                    r.kill(runner);
                })
            } else if opts.cmd[0] == "connect" {
                let mut hosts: Vec<String> = vec![];
                let a = acc.search_host(&opts.host_key);
                for x in a {
                    hosts.push(format!("http://{}:54321", &x.host))
                }
                // let hosts_str = hosts.join(",");
                let repl = json_to(
                    "host",
                    &hosts.join(","),
                    "add",
                    "/brd",
                    Some(&opts.server),
                    Some(&cipher),
                    Some(opts.brd),
                );
                log::info!("\n{}", repl.msg.on_green());
            } else if opts.cmd[0] == "check" {
                acc.search_host(&opts.host_key).with(move |mut remote| {
                    remote.check(vec![
                        "python", "python3", "pip3", "wget", "curl", "ssserver", "apt-get",
                        "unzip", "git",
                    ])
                });
            } else if opts.cmd[0] == "down" {
                match opts.cmd.len() {
                    2 => acc.search_host(&opts.host_key).down(&opts.cmd[1], None),
                    3 => acc
                        .search_host(&opts.host_key)
                        .down(&opts.cmd[1], Some(&opts.cmd[2])),
                    _ => log::error!("{}", "url must special !".red().bold()),
                }
            } else if opts.cmd[0] == "update" {
                let config_file = dirs::home_dir().unwrap().join(".mu-cache/config.toml");
                acc.search_host(&opts.host_key)
                    .upload(config_file.to_str().unwrap(), ".mu-cache/config.toml");
            } else if opts.cmd[0] == "get" && opts.cmd.len() > 1 {
                // let dst = opts.cmd[2].clone();
                for f_str in &opts.cmd[1..] {
                    let f_str = f_str.clone();
                    acc.search_host(&opts.host_key).download(&f_str);
                }
            } else if opts.cmd[0] == "exec" && opts.cmd.len() > 2 {
                let up_executor = opts.cmd[1].clone();
                let cmd_str = opts.cmd[2..].join(" ");
                acc.search_host(&opts.host_key).upload_exec(
                    &up_executor,
                    &cmd_str,
                    move |remote| {
                        log::info!("{}|\n{}", &remote.addr().blue(), &remote.res.green());
                    },
                );
            } else {
                let cmd_str = opts.cmd.join(" ");
                acc.search_host(&opts.host_key)
                    .exec(&cmd_str, move |remote: RemoteAccount| {
                        log::info!("{}|\n{}", &remote.addr().blue(), &remote.res.green());
                    });
            }
        }
        std::process::exit(0);
    }
    
    if opts.uninstall {
        let h = home(false);
        fs::remove_dir_all(&h).expect("alread remvoe ?");
    } else if opts.test {
        println!("----- test encryptor data ----- \n");
        test_enc();

        let res = json_to(
            "up",
            "Data hahahah",
            "test1",
            "/db",
            None,
            None,
            Some(opts.brd),
        );
        println!("----- test no encrypt insert data ----- \n {}", res);

        let res = json_to(
            "up",
            "sencod line data",
            "test2",
            "/db",
            None,
            Some(&cipher),
            Some(opts.brd),
        );
        println!("----- test no encrypt insert data ----- \n {}", res);

        let res = json_to("sr", "test", "", "/db", None, Some(&cipher), Some(opts.brd));
        println!("----- test search  ----- \n{}", res);

        let res = json_to(
            "ls",
            "test1",
            "",
            "/db",
            None,
            Some(&cipher),
            Some(opts.brd),
        );
        println!("----- test ls keys  ----- \n{}", res);

        let res = json_to(
            "co",
            "test1",
            "",
            "/db",
            None,
            Some(&cipher),
            Some(opts.brd),
        );
        println!("----- test count datas  ----- \n{}", res);

        let res = json_to(
            "rm",
            "test1",
            "",
            "/db",
            None,
            Some(&cipher),
            Some(opts.brd),
        );
        println!("----- test rm  ----- \n{}", res);

        let res = json_to(
            "rsdm",
            "test1",
            "",
            "/db",
            None,
            Some(&cipher),
            Some(opts.brd),
        );
        println!("----- test help ----- \n{}", res);
    } else if opts.client.len() > 0 {
        if !opts.host_key.contains("[NONE]") {
            let home = dirs::home_dir().unwrap();
            let p = home.join(".config").join("seed").join("cache.db");
            if let Some(acc) = AccountManager::new(p.as_ref()) {
                // let mut hosts: Vec<String> = vec![];
                let a = acc.search_host(&opts.host_key);
                for x in a {
                    log::info!(
                        "{}{}",
                        &x.host.black().on_yellow(),
                        &opts.client.join("|").bright_white().bold().on_green()
                    );
                    client_main(&opts, &format!("http://{}:54321", &x.host));
                }
            }
        } else if opts.client.len() == 1 {
            if let Ok(_) =  Uuid::parse_str(&opts.client[0]){
                let uuid_str = opts.client[0].clone();
                if opts.brd{
                    opts.client[0] = "/brd".to_string();
                    opts.client.push("ask".to_string());
                    opts.client.push(uuid_str.clone());
                    client_main(&opts, &opts.server);
                }
                
                opts.client[0] = "/task".to_string();
                opts.client.push("view".to_string());
                opts.client.push(uuid_str);
                client_main(&opts, &opts.server);    
            }

        } else {
            client_main(&opts, &opts.server);
        }
    } else {
        if opts.daemon {
            use std::env;
            let args: Vec<String> = env::args().collect();
            let addr = opts.server.replace("http://", "");
            daemon_start(&format!("{} -s {} ", &args[0], &addr), &opts.log);
        } else {
            run_server(&opts.server);
        }
    }
}
